contents
비교 연산자는 두 값(피연산자) 간의 관계를 평가하여 불리언(true 또는 false) 결과를 반환합니다. 기호는 거의 보편적이지만, 객체 대 기본 값(primitive)에 대한 연산자의 정확한 의미는 언어마다 상당히 다릅니다.
6가지 표준 비교 연산자는 다음과 같습니다.
==(같음)!=(같지 않음)>(보다 큼)<(보다 작음)>=(보다 크거나 같음)<=(보다 작거나 같음)
각 언어별 구현과 미묘한 차이점을 자세히 설명해 드립니다.
C
C언어에서 비교는 직설적이지만, 최신 언어와는 한 가지 큰 차이점이 있습니다. C의 "불리언"은 단지 정수일 뿐입니다.
- 연산자:
==,!=,>,<,>=,<= - 반환 값: C(C99 이전)에는 내장
bool타입이 없습니다. 대신 정수를 사용합니다.0은 false를 나타냅니다.0이 아닌 모든 정수는 true를 나타냅니다.- 비교 연산자는 true일 경우
1을, false일 경우0을 반환합니다.
- 핵심 사항: 문자열 비교
C 스타일 문자열(char* 또는 char[])의 내용을 비교하는 데 ==를 사용할 수 없습니다. == 연산자는 문자열의 내용이 아닌 포인터 주소(메모리 위치)만 비교합니다.
- 잘못된 사용:
if (str1 == str2) - 올바른 사용:
if (strcmp(str1, str2) == 0)(<string.h>라이브러리 사용)
- 잘못된 사용:
C++
C++은 C를 기반으로 하며, 진정한 불리언 타입과 연산자 오버로딩을 추가했습니다.
- 연산자:
==,!=,>,<,>=,<= - 반환 값: C++에는
true와false키워드를 가진 네이티브bool타입이 있습니다. - 핵심 사항: 연산자 오버로딩 이것이 가장 중요한 기능입니다. 클래스는 비교 연산자를 오버로딩하여 사용자 정의 로직을 정의할 수 있습니다. 이를 통해 ==나 <가 사용자 정의 객체에 대해 어떤 의미를 가질지 정의할 수 있습니다.
- 문자열 비교:
std::string클래스가==연산자를 오버로딩하기 때문에,==를 사용하여 문자열 내용을 직접 비교할 수 있습니다.
std::string s1 = "hello";
std::string s2 = "hello";
if (s1 == s2) { // 완벽하게 작동합니다.
// ...
}
- 포인터 비교: C와 마찬가지로, 원시 포인터에
==를 사용하면 메모리 주소를 비교합니다.
Java
Java에서는 기본 타입(primitive)과 객체(object)의 구분이 여기서 가장 중요한 세부 사항입니다.
- 연산자:
==,!=,>,<,>=,<= - 핵심 사항:
==vs..equals()이는 초보자들이 흔히 겪는 버그의 고전적인 원인입니다.- 기본 타입 (
int,double,boolean,char)의 경우:==는 예상대로 실제 값을 비교합니다. - 객체 타입 (
String,Integer, 모든 사용자 정의 클래스)의 경우:==는 메모리 주소(참조)를 비교합니다. 두 변수가 메모리상에서 정확히 동일한 객체 를 가리키는지 확인합니다. 두 객체의 내부 내용 을 비교하려면 반드시.equals()메서드를 사용해야 합니다.
- 기본 타입 (
// 기본 타입 예제
int a = 5;
int b = 5;
// a == b 는 TRUE
// 객체 예제
String s1 = new String("hi");
String s2 = new String("hi");
// s1 == s2 는 FALSE (메모리상 다른 객체임)
// s1.equals(s2) 는 TRUE (내용이 동일함)
Python
Python은 ==가 값을 확인하고, 별도의 연산자(is)가 동일성을 확인하도록 하여 단순화했습니다.
- 연산자:
==,!=,>,<,>=,<= - 핵심 사항:
==vs.is==(값 동등성): 거의 항상 원하는 것입니다. 두 피연산자의 값 이 같은지 확인합니다. 내부적으로는__eq__메서드를 호출합니다.is(동일성): 두 변수가 메모리상에서 정확히 동일한 객체 를 가리키는지 확인합니다. 이는 Java의 객체에 대한==와 동일합니다.
a = [1, 2, 3]
b = [1, 2, 3]
# a == b 는 True (값이 같음)
# a is b 는 False (메모리상 별개의 리스트 객체임)
c = a
# a is c 는 True (둘 다 동일한 객체를 가리킴)
- 연산자 오버로딩: Python은 비교 로직을 정의하기 위해
__eq__,__lt__,__ge__등과 같은 "던더"(이중 밑줄) 메서드를 사용합니다.
Kotlin
Kotlin은 Java의 == 혼란을 "수정"하기 위해 설계되었습니다.
- 연산자:
==,!=,>,<,>=,<= - 핵심 사항:
==vs.=====(구조적 동등성): 이것이 "안전한" 기본값입니다. 객체의 경우, 자동으로.equals()메서드에 대한 null-safe 호출로 변환됩니다. 값/내용을 비교합니다.===(참조 동등성): 두 변수가 메모리상에서 정확히 동일한 객체 를 가리키는지 명시적으로 확인하고 싶을 때 사용하는 연산자입니다. 이는 Java의 객체에 대한==와 동일합니다.
val s1 = "hi"
val s2 = "hi"
// s1 == s2 는 TRUE (.equals()를 호출하며, 결과가 true임)
val a = Integer(10)
val b = Integer(10)
// a == b 는 TRUE
// a === b 는 FALSE (서로 다른 객체임)
Rust
Rust는 트레이트(trait) 시스템을 통해 비교를 처리하여 컴파일 타임에 타입 안전성을 보장합니다.
- 연산자:
==,!=,>,<,>=,<= - 핵심 사항:
Eq,PartialEq,Ord,PartialOrd트레이트==와 같은 연산자는 트레이트에 정의된 메서드의 단축키일 뿐입니다. 연산자가 작동하려면 해당 타입이 대응하는 트레이트를 구현해야 합니다.==와!=:PartialEq트레이트가 필요합니다. 이 트레이트는eq()와ne()메서드를 제공합니다. 부동 소수점NaN(숫자 아님)처럼 자기 자신과도 같지 않은 값(NaN == NaN은 false)이 있기 때문에 "부분적(partial)"이라고 불립니다.Eq트레이트는 (a == a가 항상 true인) 재귀적 동등성이 성립하는 타입을 위한 추가 마커입니다.>,<,>=,<=:PartialOrd트레이트(부분적 순서)가 필요합니다.Ord트레이트는 (정수처럼) 전체 순서를 갖는 타입을 위한 것입니다. 대부분의 간단한struct의 경우, 컴파일러가 이러한 트레이트를 자동으로 구현하도록 할 수 있습니다.
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
struct User {
id: i32,
name: String,
}
fn main() {
let user1 = User { id: 1, name: "Alice".to_string() };
let user2 = User { id: 1, name: "Alice".to_string() };
let user3 = User { id: 2, name: "Bob".to_string() };
// PartialEq를 파생시켰기 때문에 작동함
if user1 == user2 {
println!("user1과 user2는 같습니다");
}
// PartialOrd를 파생시켰기 때문에 작동함
if user3 > user1 {
println!("user3이 user1보다 큽니다");
}
}
요약 표
| 언어 | 값 동등성 (내용) | 동일성 (참조) |
|---|---|---|
| C | strcmp(s1, s2) == 0 (문자열의 경우) |
ptr1 == ptr2 |
| C++ | s1 == s2 (오버로딩된 경우, 예: std::string) |
ptr1 == ptr2 |
| Java | obj1.equals(obj2) |
obj1 == obj2 |
| Python | obj1 == obj2 |
obj1 is obj2 |
| Kotlin | obj1 == obj2 (null-safe .equals()) |
obj1 === obj2 |
| Rust | obj1 == obj2 (PartialEq 트레이트 필요) |
std::ptr::eq(ptr1, ptr2) (명시적) |
references